home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
TPUG - Toronto PET Users Group
/
TPUG Users Group CD
/
TPUG Users Group CD.iso
/
AMIGA
/
(A)TA
/
(A)TAV.ADF
/
MRPrint
/
MRPrint.c
< prev
next >
Wrap
C/C++ Source or Header
|
1988-11-22
|
19KB
|
789 lines
/* :ts=4 */
/*
MRPrint: detabbing text file printer for the Amiga
Author: Mark Rinfret (Usenet: mrr@amanpt1; Bix: markr)
I am offering this to the Amiga user community without restrictions.
If you make improvements, please re-release with source. Enjoy!
This program will print text files containing embedded tabs and
form feeds. Though the default tab setting is 4, the user may
override this to some other value as necessary. MRPrint will also
optionally output a page header containing the filename, current
date and time, line number and page number. MRPrint supports variable
margins and will enforce them. Line numbers will be printed if
requested. Note that by default, MRPrint prints to PRT:. If you wish
to redirect output, be sure to use the "-s" option.
Usage: pr [-l] [-n#] [-t#] [-h] [file1] file2] ...
options:
-h do not print a page header
-l print with line numbers
-L# set left margin to #
-n# print # lines per page
-R# set right margin to #
-s print to standard output
-t# set tab to # spaces (default 4)
Handles ARP wildcarding.
05/13/88 -MRR- Yeah, I know - version 3.0 didn't last very long.
I observed the output with the -s option and decided
that the single character I/O I was doing was very
unacceptable. This version buffers both input and
output.
05/12/88 -MRR- THIS PROGRAM HAS BEEN ARPIFIED! What the hell,
I've been wanting to dig into ARP for quite a
while. Now that I have V1.1 of ARP, V3.6 of Manx
and a day off, this was as good a program as any
to do some exploring.
*/
#define AMIGA
/* #define DEBUG */
#include <stdio.h>
#include <ctype.h>
#include <libraries/arpbase.h>
#include <arpfunctions.h>
#include <functions.h>
#define VERSION "pr version 3.1, 05/13/88 (requires ARP V1.1 or higher)"
#define INBUFSIZE 4096L /* input buffer size */
#define MAXLINE 256
#define OUTBUFSIZE 2048L /* output buffer size */
#define yes 1
#define no 0
#define SizeOf(x) ((ULONG) sizeof(x))
/* An extended AnchorPath structure to enable full pathnames to be
* generated by FindFirst, FindNext.
*/
struct UserAnchor {
struct AnchorPath ua_AP;
BYTE moreMem[255];
};
char *FGets(); /* AmigaDOS/ARP compatible version. */
char *NextFile();
void PutNumber();
void PutOneChar();
void PutString();
unsigned abort; /* Set by CTRL-C, really unnecessary. */
struct UserAnchor *anchor; /* Used by FindFirst, FindNext */
struct DateTime *dateAndTime; /* Go ahead - take a wild guess. */
char dateStr[20], timeStr[20];
unsigned doLineNumbers = no;
unsigned endOfInput;
BPTR f; /* The current input file (handle) */
char *fileName; /* The name of the input file. */
unsigned forcePage; /* Set by \f. */
unsigned headers = yes; /* Controls page header generation. */
UBYTE *inBuf,*inBufPtr; /* Input buffer, sliding pointer */
unsigned inBufCount, inBufLength;
unsigned leftMargin = 5;
unsigned lineNumber;
unsigned linesPerPage = 55;
UBYTE *outBuf, *outBufPtr;/* Output buffer, sliding pointer */
unsigned outBufLength; /* Length of output buffer. */
unsigned pageNumber;
BPTR printer; /* Output device/file handle. */
static char *prtname = "PRT:";
LONG result; /* Result of wildcard processing. */
unsigned rightMargin = 85;
unsigned srcLine; /* Current source file line number. */
unsigned tabSpace = 4; /* How many spaces 1 tab equals. */
unsigned tabStops[MAXLINE]; /* Computed tab stops. */
unsigned useRequester = no; /* Get filenames with requester? */
unsigned useStdOut = no; /* Print to standard output? */
unsigned xargc; /* arg count after option processing */
char **xargv; /* arg vector after option processing */
^L
/* This is where all goodness begins. Actually, I'm not too happy with
* the size of the main program. It ought to be broken up (or down :-).
*/
main (argc, argv)
int argc; char *argv[];
{
unsigned i;
char *s;
if (argc) { /* zero if started from workbench */
++argv; /* skip over program name arg */
--argc;
/* ..process switches.. */
for (; *(s = *(argv)) == '-'; ++argv, --argc) {
while (*++s)
switch (*s) {
case '?':
Usage();
case 'l':
doLineNumbers = yes;
break;
case 'L':
if ((leftMargin = Atol (s + 1)) <= 0) {
Abort("Bad left margin ", (long) leftMargin);
}
goto next_arg; /* Oh my gawd! A GOTO! */
case 'n':
linesPerPage = Atol (s + 1);
goto next_arg; /* Oh no! A nuther one! */
break;
case 'R':
if ((rightMargin = Atol (s + 1)) <= 0 ||
rightMargin > MAXLINE) {
Abort("Bad right margin ", (long) rightMargin);
}
goto next_arg; /* It's a bloody epidemic! */
case 's':
useStdOut = yes;
break;
case 't':
if ((tabSpace = Atol (s + 1)) <= 0) {
Abort("Bad tab specification ", (long) tabSpace);
}
goto next_arg; /* This is disgusting! */
case 'h':
headers = no;
break;
case 'v':
Printf("\n%s\n", VERSION);
break;
default:
Usage();
}
/* Gag! A label! There must be some goto's sneakin' around... */
next_arg: ;
}
}
/* Check a few argument combinations. */
if (leftMargin >= rightMargin) {
Abort("Left margin >= right margin? Ha ha!", 0L);
}
if (doLineNumbers)
leftMargin = 5; /* No margins with numbering but numbers
use 5 columns. */
SetTabs(); /* Initialize tab settings. */
/* Allocate input and output buffers. */
inBuf = ArpAlloc(INBUFSIZE);
if (inBuf == NULL)
Abort("No memory for input buffer!", INBUFSIZE);
outBuf = ArpAlloc(OUTBUFSIZE);
if (outBuf == NULL)
Abort("No memory for output buffer!", OUTBUFSIZE);
/* Get the date and time; we might need it. */
dateAndTime = (struct DateTime *) ArpAlloc(SizeOf(*dateAndTime));
if (dateAndTime == NULL) {
Abort("No memory!", SizeOf(*dateAndTime));
}
DateStamp(dateAndTime);
dateAndTime->dat_Format = FORMAT_USA;
dateAndTime->dat_StrDate = dateStr;
dateAndTime->dat_StrTime = timeStr;
StamptoStr(dateAndTime);
if (useStdOut)
printer = (BPTR) Output();
else
if ((printer = ArpOpen(prtname, MODE_NEWFILE)) == NULL) {
Abort("Failed to open printer ", IoErr());
}
/* Process files. */
xargv = argv;
if ((xargc = argc) == 0) /* If no filename args, use requester. */
useRequester = yes;
else {
if ((anchor = (struct UserAnchor *)
ArpAlloc(SizeOf(*anchor))) == NULL) {
Abort("No memory!", SizeOf(*anchor));
}
anchor->ua_AP.ap_Length = 255; /* Want full path built. */
anchor->ua_AP.ap_BreakBits |=
(SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D);
result = ERROR_NO_MORE_ENTRIES;
}
while (!abort && (fileName = NextFile()) ) {
if ((f = (BPTR) Open(fileName, MODE_OLDFILE)) != NULL) {
PrintFile();
Close(f);
f = NULL;
}
else
Printf("\n*** MRPrint: Can't open %s for printing ***\n",
fileName);
}
}
/* Abort the program.
* Called with:
* desc: descriptive text
* code: error code (printed if non-zero)
* Returns:
* to the system, where else?!
*/
Abort(desc, code)
char *desc; long code;
{
Printf("\n*** MRPrint aborting: %s", desc);
if (code)
Printf(" (%ld) ", code);
Puts(" ***");
if (f) Close(f); /* File open? Close it. */
ArpExit(20L, 0L);
}
/* Print one file. */
PrintFile()
{
char line[MAXLINE];
forcePage = pageNumber = srcLine = 0;
lineNumber = linesPerPage;
inBufPtr = inBuf;
inBufLength = 0;
inBufCount = 0;
outBufPtr = outBuf;
outBufLength = 0;
endOfInput = no;
while (FGets (line, MAXLINE - 1, f) != NULL && !abort ) {
++srcLine; /* count input lines */
/* Note that top-of-form detection was a rather kludgy addition. It only
* works if the first character in the line is a ^L.
*/
if (*line == '\f') {
*line = ' '; /* replace embedded ^L with blank */
lineNumber = linesPerPage;/* force new page */
}
if (lineNumber >= linesPerPage)
Header();
DeTab(line); /* ..output detabbed line.. */
}
PutOneChar('\f'); /* ..form-feed after last page.. */
FlushBuffer();
}
/* An attempt has been made to print a line past the right margin.
* Crash the user's system and melt his...naw, force a new line and
* output a new left margin. Also, if the page line count has been
* exceeded, start a new page.
*/
BreakLine()
{
PutOneChar('\n');
if (++lineNumber > linesPerPage)
Header();
DoLeftMargin();
}
/* Output a dashed line according to an obscure formula derived through
* intense empirical analysis while listening to the tune
*
* "Camptown ladies sing this song, DoDash, DoDash..."
*/
DoDash()
{
PutMany(' ', leftMargin);
PutMany('-', rightMargin - leftMargin - 5);
PutOneChar('\n');
}
/* Output spaces for the left margin, or a source line number, whatever
* tickles the user's fanny....fancy!
*/
DoLeftMargin()
{
unsigned i;
if (doLineNumbers) {
PutNumber(srcLine, 4);
PutOneChar(' ');
}
else
PutMany(' ', leftMargin);
}
/* Noch ein bier, bitte, mit der grosse kopf.
* That's Deutch for "Put a header on this page, please!".
*/
Header()
{
int i;
if (++pageNumber != 1) {
PutOneChar('\f'); /* Eject if not first page. */
PutOneChar('\n');
}
if (headers) {
DoDash();
/* Note: there's room for improvement here. A fancier algorithm would
* attempt to distribute this information evenly over the current page
* width. A less lazy programmer would have written the fancier algorithm.
*/
DoLeftMargin();
PutString(fileName);
PutMany(' ', 2);
PutString(dateStr);
PutMany(' ', 2);
PutString(timeStr);
PutString(" Page ");
PutNumber(pageNumber, 0);
PutString(" Line ");
PutNumber(srcLine, 0);
PutOneChar('\n');
DoDash();
PutString("\n");
}
lineNumber = 0;
}
/* Replace embedded tab characters with the appropriate number of spaces,
* outputting the results to the output device/file.
* Called with:
* line: string on which to do replacements
* Returns:
* eventually :-)
*/
DeTab(line) /* DeTab is not as good as DePepsi. */
char *line;
{
int eol = 0, i, col;
DoLeftMargin();
col = leftMargin;
/* Note: line[] has a terminating '\n' from fgets()...except if
* the input line length exceeded MAXLINE.
*/
for (i = 0; i < strlen (line); ++i)
if (line[i] == '\t') { /* ..tab.. */
do {
if (col == rightMargin) {
BreakLine();
break;
}
PutOneChar(' ');
++col;
} while (!tabStops[col]);
}
else {
if (line[i] == '\n')
++eol;
else
if (col == rightMargin)
BreakLine();
PutOneChar(line[i]);
++col;
}
if (!eol)
PutOneChar('\n'); /* no end of line? */
++lineNumber;
}
/* Initialize the tab settings for this file. */
SetTabs()
{
int i;
for (i = 0; i < MAXLINE; ++i)
tabStops[i] = (i % tabSpace == 1);
}
/* Display correct program Usage, then exit. */
Usage()
{
register unsigned i;
register char *s;
static char *usageText[] = {
"Usage: pr [-l] [-n#] [-t#] [-h] [-v] [file1] file2] ...",
"\toptions:",
"\t\t-h do not print page headers",
"\t\t-l print with line numbers",
"\t\t-L# set left margin to #",
"\t\t-n# print # lines per page",
"\t\t-R# set right margin to #",
"\t\t-s print to standard output instead of PRT:",
"\t\t-t# set tab to # spaces (default 4)",
"\t\t-v display program version number",
"ARP wildcarding is supported.",
(char *) NULL /* last entry MUST be NULL */
};
for (i = 0; s = usageText[i]; ++i)
Puts(s);
ArpExit(20L, 0L);
}
/* Get the next file name, either from the argument list or via a
* requester.
*/
char*
NextFile()
{
#define NUMBEROFNAMES 10L
static struct FileRequester request;
static char dName[DSIZE*NUMBEROFNAMES+1] = "";
static char fName[FCHARS+1] = "";
struct FileLock *lock;
if (useRequester) {
if (request.fr_File == NULL) {
request.fr_File = fName;
/* To get the current directory path, get a lock on it, then
* use PathName to convert it to a full path.
*/
lock = Lock("", ACCESS_READ);
PathName(lock, dName, NUMBEROFNAMES);
UnLock(lock);
request.fr_Dir = dName;
request.fr_Hail = "Select file to print:";
}
return FileRequest(&request);
}
/* Note: result is initialized to ERROR_NO_MORE_ENTRIES prior to
* calling this routine for the first time.
*/
while ((result == 0) || (result == ERROR_NO_MORE_ENTRIES)) {
if (result == 0) { /* Working a pattern? */
if ((result = FindNext(anchor)) == 0L) {
if (SkipDirEntry(anchor)) continue;
break;
}
}
if (result == ERROR_NO_MORE_ENTRIES) {
if (xargc <= 0) {
result = -1;
break;
}
result = FindFirst(*xargv, anchor);
++xargv; /* Advance arg list pointer. */
--xargc; /* One less arg to process. */
if (result == 0) {
if (SkipDirEntry(anchor)) continue;
break;
}
}
/* Only one error code is acceptable: */
if (result && (result != ERROR_NO_MORE_ENTRIES)) {
Printf("\n*** MRPrint I/O error %ld on pattern %s ***\n",
result, *xargv);
result = 0; /* Allow another pass. */
}
}
/* Return filename or NULL, depending upon result. */
return (result == 0 ? (char *) &anchor->ua_AP.ap_Buf : NULL);
}
/* Read one line (including newline) from the input file.
* Called with:
* line: string to receive text
* maxLength: maximum length of string
* f: AmigaDOS file handle bee pointer (BPTR, ya' know).
*/
char *
FGets(line, maxLength, f)
char *line; int maxLength; BPTR f;
{
char *buf = line;
int c;
int lineLength = 0;
if (abort = CheckAbort(NULL)) {
PutString("\n^C\f");
Abort("^C", 0L);
}
while (lineLength < maxLength) {
if ( ( c = GetOneChar(f) ) < 0 ) break;
++lineLength;
if ((*buf++ = c) == '\n') break; /* Stop on end of line. */
}
line[lineLength] = '\0';
if (c < -1) {
/* Report the error to the printer and the console, but don't
* give up on the rest of the files. I think they call that
* being user friendly.
*/
c = -c; /* Invert the error code. */
Printf("*** I/O error on input %d ***\n", c);
if (!useStdOut) {
PutString("*** Input I/O error");
PutNumber(c, 0);
PutString("***\n");
}
lineLength = 0;
}
return (lineLength == 0 ? NULL : line);
}
/* Flush the printer (output) buffer (phew!). */
FlushBuffer()
{
long actualLength;
long ioResult;
if (outBufLength) {
actualLength = Write(printer, outBuf, (long) outBufLength);
if (actualLength != outBufLength) {
ioResult = IoErr();
Abort("Output error!", ioResult);
}
}
outBufPtr = outBuf;
outBufLength = 0;
}
/* Get one character from the input stream. If the input buffer is
* exhausted, attempt to get some more input. If this is the first
* input buffer for this file, check the buffer for binary content.
* Called with:
* f: input file handle
* Returns:
* character code (>= 0) or status (< 0, -1 => end of input)
*/
int
GetOneChar(f)
BPTR f;
{
int ioStatus;
if (endOfInput)
return -1;
if (inBufLength <= 0 ) {
inBufLength = Read(f, inBuf, INBUFSIZE);
/* If this is the first buffer, test it for binary content.
* If the file is binary, skip it by setting the actualLength
* to zero (simulate end of file).
*/
if ((++inBufCount == 1) && inBufLength > 0) {
if (SkipBinaryFile(anchor))
inBufLength = 0;
}
if (inBufLength <= 0) {
if (inBufLength == -1)
ioStatus = -IoErr();
else {
ioStatus = -1;
endOfInput = yes;
}
return ioStatus;
}
inBufPtr = inBuf;
}
--inBufLength;
return *inBufPtr++;
}
/* Put multiple copies of a character into the output buffer (repeat).
* Called with:
* c: character to be repeated
* n: number of copies
* Returns:
* tired but satisfied
*/
PutMany(c, n)
int c, n;
{
for( ; n > 0; --n)
PutOneChar(c);
}
/* Output a simple formatted unsigned number.
* Called with:
* number: value to be formatted
* length: number of digits desired (0 => doesn't matter)
*/
void
PutNumber(number, length)
unsigned number, length;
{
unsigned digitCount = 0, i;
char digits[6];
do {
digits[digitCount++] = (number % 9) + '0';
number /= 10;
} while (number);
while (length > digitCount) {
PutOneChar(' ');
--length;
}
do {
PutOneChar(digits[--digitCount]);
} while (digitCount);
}
/* Output one character to the printer device/file.
* Called with:
* c: character to be output
* Returns:
* nada
*/
void
PutOneChar(c)
int c;
{
if (outBufLength >= OUTBUFSIZE)
FlushBuffer();
*outBufPtr++ = c;
++outBufLength;
}
/* Output a string to the printer device/file.
* Called with:
* s: string to output
* Returns:
* when it's done, of course!
*/
void
PutString(s)
char *s;
{
register int c;
register char *s1;
for (s1 = s; c = *s1; ++s1)
PutOneChar(c);
}
/* Test the contents of the first buffer for binary data. If the buffer
* is determined to have binary content, tell the user that we are
* skipping the file. This allows the user to give a single wildcard
* specification without worrying about printing object, data and program
* files (assuming, of course, that binary data is detected within the
* first INBUFSIZE bytes of the file).
* Called with:
* anchor: pointer to UserAnchor structure describing the file
* Returns:
* yes: file contains binary
* no: file is text (we think)
*/
int
SkipBinaryFile(anchor)
struct UserAnchor *anchor;
{
char *strchr();
/* The following string describes binary characters that are considered
* to be "OK". These are, from left to right:
*
* newline, form feed, tab, carriage return, backspace
*
*/
static char *okSpecial = "\n\f\t\015\010";
register UBYTE c;
register int i;
int isBinary = no;
for (i = 0; i < inBufLength; ++i)
if (((c = inBuf[i]) < ' ') || c > 0x7F) {
if (!strchr(okSpecial, c)) {
isBinary = yes;
break;
}
}
if (isBinary) {
Printf("\n*** MRPrint: skipping binary file %s ***\n", fileName);
}
return isBinary;
}
/* Test the file described by the anchor parameter for "directoryness".
* If it's a directory, print a message that we're skipping it.
* Called with:
* anchor: file entry info returned by FindFirst, FindNext
* Returns:
* yes: file is a directory
* no: file is a file (astonishing, eh?)
*/
int
SkipDirEntry(anchor)
struct UserAnchor *anchor;
{
if (anchor->ua_AP.ap_Info.fib_DirEntryType >= 0) {
Printf("\n*** MRPrint: skipping directory %s ***\n",
&anchor->ua_AP.ap_Buf);
return yes;
}
return no;
}